﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace CNative.Utilities
{
    /// <summary>
    /// Table转换实体处理
    /// </summary>
    public static class FuncTable2Entity
    {
        private delegate object LoadType(DataRow DrRecord, Type type);

        private delegate T Load<T>(DataRow DrRecord);
        private static readonly MethodInfo mGetValueMet = typeof(DataRow).GetMethod("get_Item", new Type[] { typeof(int) });
        private static readonly MethodInfo mIsDBNullMet = typeof(DataRow).GetMethod("IsNull", new Type[] { typeof(int) });
        private static Dictionary<Type, Delegate> mRowMapMets = new Dictionary<Type, Delegate>();
        private static Dictionary<Type, MethodInfo> mConvertMets = new Dictionary<Type, MethodInfo>()
       {
           {typeof(int),typeof(FuncStr).GetMethod("NullToInt",new Type[]{typeof(object)})},
           {typeof(Int16),typeof(Convert).GetMethod("ToInt16",new Type[]{typeof(object)})},
           {typeof(Int64),typeof(Convert).GetMethod("ToInt64",new Type[]{typeof(object)})},
           {typeof(DateTime),typeof(Convert).GetMethod("ToDateTime",new Type[]{typeof(object)})},
           //  {typeof(DateTime?),typeof(Convert).GetMethod("ToDateTime",new Type[]{typeof(object)})},
           {typeof(decimal),typeof(FuncStr).GetMethod("NullToDecimal",new Type[]{typeof(object)})},
           {typeof(double),typeof(FuncStr).GetMethod("NullToDouble",new Type[]{typeof(object)})},
           {typeof(Boolean),typeof(Convert).GetMethod("ToBoolean",new Type[]{typeof(object)})},
           {typeof(char),typeof(Convert).GetMethod("ToChar",new Type[]{typeof(object)})},
           {typeof(string),typeof(Convert).GetMethod("ToString",new Type[]{typeof(object)})},
           {typeof(byte),typeof(Convert).GetMethod("ToByte",new Type[]{typeof(object)})},
           {typeof(Single),typeof(Convert).GetMethod("ToSingle",new Type[]{typeof(object)})}
       };
        internal static TEnum ToEnum<TEnum, TUnder>(object obj)
        {
            return (TEnum)Convert.ChangeType(obj, typeof(TUnder));
        }
        internal static TEnum StrToEnum<TEnum>(object value) where TEnum : struct
        {
            if (Enum.TryParse<TEnum>(value.NullToStr(), out TEnum enumStr))
            {
                return enumStr;
            }

            return default(TEnum);
        }

        internal static T ToIntCuInt<T>(object obj)
        {
            return (T)Convert.ChangeType(obj, typeof(int));
        }

        internal static T ToIntCuDecimal<T>(object obj)
        {
            return (T)Convert.ChangeType(obj, typeof(decimal));
        }
        internal static T ToIntCuDouble<T>(object obj)
        {
            return (T)Convert.ChangeType(obj, typeof(double));
        }
        internal static T ToIntCuLong<T>(object obj)
        {
            return (T)Convert.ChangeType(obj, typeof(long));
        }
        internal static T ToIntCuDateTime<T>(object obj)
        {
            return (T)Convert.ChangeType(obj, typeof(DateTime));
        }

        internal static T ToGuid<T>(object obj)
        {
            return (T) Convert.ChangeType(obj, typeof(Guid));
        }
        private static DynamicMethod getDynamicMethod(DataTable _tab, Type type)
        {
            if (_tab == null) return null;
            DynamicMethod _Method = null;
            //if (!mRowMapMets.TryGetValue(type,out _Method))
            //{
            _Method = new DynamicMethod("DyEntity_" + type.Name, type, new Type[] { typeof(DataRow) }, type, true);
            ILGenerator _Generator = _Method.GetILGenerator();
            LocalBuilder _Result = _Generator.DeclareLocal(type);
            _Generator.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes));
            _Generator.Emit(OpCodes.Stloc, _Result);
            for (int i = 0; i < _tab.Columns.Count; i++)
            {
                PropertyInfo propertyInfo = type.GetProperties().ToList().Find(p => p.Name.ToLower().Equals(_tab.Columns[i].ColumnName.ToLower()));
                System.Reflection.Emit.Label endIfLabel = _Generator.DefineLabel();
                if (propertyInfo != null && propertyInfo.GetSetMethod() != null)
                {
                    _Generator.Emit(OpCodes.Ldarg_0);
                    _Generator.Emit(OpCodes.Ldc_I4, i);
                    _Generator.Emit(OpCodes.Callvirt, mIsDBNullMet);
                    _Generator.Emit(OpCodes.Brtrue, endIfLabel);
                    _Generator.Emit(OpCodes.Ldloc, _Result);
                    _Generator.Emit(OpCodes.Ldarg_0);
                    _Generator.Emit(OpCodes.Ldc_I4, i);
                    _Generator.Emit(OpCodes.Callvirt, mGetValueMet);
                    if (propertyInfo.PropertyType.IsValueType || propertyInfo.PropertyType == typeof(string))
                        if (propertyInfo.PropertyType.IsEnum)
                            _Generator.Emit(OpCodes.Call, typeof(FuncTable2Entity).GetMethod("ToEnum", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(propertyInfo.PropertyType, Enum.GetUnderlyingType(propertyInfo.PropertyType)));
                        else if (propertyInfo.PropertyType == typeof(int?))
                            _Generator.Emit(OpCodes.Call, typeof(FuncTable2Entity).GetMethod("ToIntCuInt", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(propertyInfo.PropertyType));
                        else if (propertyInfo.PropertyType == typeof(long?))
                            _Generator.Emit(OpCodes.Call, typeof(FuncTable2Entity).GetMethod("ToIntCuLong", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(propertyInfo.PropertyType));
                        else if (propertyInfo.PropertyType == typeof(decimal?))
                            _Generator.Emit(OpCodes.Call, typeof(FuncTable2Entity).GetMethod("ToIntCuDecimal", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(propertyInfo.PropertyType));
                        else if (propertyInfo.PropertyType == typeof(double?))
                            _Generator.Emit(OpCodes.Call, typeof(FuncTable2Entity).GetMethod("ToIntCuDouble", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(propertyInfo.PropertyType));
                        else if (propertyInfo.PropertyType == typeof(DateTime?))
                            _Generator.Emit(OpCodes.Call, typeof(FuncTable2Entity).GetMethod("ToIntCuDateTime", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(propertyInfo.PropertyType));
                        else if (propertyInfo.PropertyType == typeof(Guid))
                            _Generator.Emit(OpCodes.Call, typeof(FuncTable2Entity).GetMethod("ToGuid", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(propertyInfo.PropertyType));
                        else
                            _Generator.Emit(OpCodes.Call, mConvertMets[propertyInfo.PropertyType]);
                    else
                        _Generator.Emit(OpCodes.Castclass, propertyInfo.PropertyType);
                    _Generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod());
                    _Generator.MarkLabel(endIfLabel);
                }
            }
            _Generator.Emit(OpCodes.Ldloc, _Result);
            _Generator.Emit(OpCodes.Ret);
            //mRowMapMets[type]=_Method;
            //}
            return _Method;
        }
        private static Load<T> GetDataRow2EntityFunc<T>(DataTable _tab) where T : class, new()
        {
            Load<T> _RowMap = null;
            if (_tab == null) return _RowMap;

            Delegate _method = null;
            if (!mRowMapMets.TryGetValue(typeof(T), out _method) || _method == null)
            {
                var _Method = getDynamicMethod(_tab, typeof(T));
                if (_Method != null)
                {
                    _RowMap = (Load<T>)_Method.CreateDelegate(typeof(Load<T>));
                    mRowMapMets[typeof(T)] = _RowMap;
                }
            }
            else
            {
                _RowMap = (Load<T>)_method;
            }

            return _RowMap;
        }
        private static LoadType GetDataRow2EntityFunc(DataTable _tab, Type type)
        {
            LoadType _RowMap = null;
            if (_tab == null) return _RowMap;

            Delegate _method = null;
            if (!mRowMapMets.TryGetValue(type, out _method) || _method == null)
            {
                var _Method = getDynamicMethod(_tab, type);
                if (_Method != null)
                {
                    _RowMap = (LoadType)_Method.CreateDelegate(typeof(LoadType));

                    mRowMapMets[type] = _RowMap;
                }
            }
            else
            {
                _RowMap = (LoadType)_method;
            }

            return _RowMap;
        }

        /// <summary>
        /// 获取列值
        /// </summary>
        /// <param name="_dr"></param>
        /// <param name="_colName">列名</param>
        /// <returns></returns>
        public static object GetColumnValue(this DataRow _dr, string _colName)
        {
            if (_dr == null || _colName.IsNullOrEmpty() || _dr.Table == null) return null;
            if (_dr.Table.Columns.Contains(_colName))
            {
                if (_dr[_colName].IsNullOrEmpty()) return null;
                return _dr[_colName];
            }
            return null;
        }
        /// <summary>
        /// 数据行复制
        /// </summary>
        /// <param name="_drFrom"></param>
        /// <param name="_drTo"></param>
        public static void DataRowCopyTo(this DataRow _drFrom, DataRow _drTo)
        {
            if (_drFrom == null || _drTo == null || _drFrom.Table == null || _drTo.Table == null) return;
            foreach (DataColumn col in _drTo.Table.Columns)
            {
                if (_drFrom.Table.Columns.Contains(col.ColumnName))
                    _drTo[col.ColumnName] = _drFrom.GetColumnValue(col.ColumnName);
            }
        }

        /// <summary>
        /// DataRow转换成Entity
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="_dr">DataRow</param>
        /// <returns></returns>
        public static T DataRow2Entity<T>(this DataRow _dr) where T : class, new()
        {
            if (_dr == null || _dr.Table == null) return default(T);

            var _RowMap = GetDataRow2EntityFunc<T>(_dr.Table);
            if (_RowMap == null) return default(T);
            return _RowMap(_dr);
        }
        /// <summary>
        /// DataRow转换成Entity
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="_dr">DataRow</param>
        /// <returns></returns>
        public static object DataRow2Entity(this DataRow _dr, Type type)
        {
            if (_dr == null || _dr.Table == null) return new object();

            var _RowMap = GetDataRow2EntityFunc(_dr.Table, type);
            if (_RowMap == null) return new object();
            return _RowMap(_dr, type);
        }
        /// <summary>
        /// DataTable转换成对象的List集合
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="_tab">DataTable</param>
        /// <returns></returns>
        public static List<T> DataTable2List<T>(this DataTable _tab) where T : class, new()
        {
            List<T> _ResultList = new List<T>();
            if (_tab == null) return _ResultList;

            var _RowMap = GetDataRow2EntityFunc<T>(_tab);
            if (_RowMap == null) return _ResultList;
            foreach (DataRow info in _tab.Rows)
                _ResultList.Add(_RowMap(info));
            return _ResultList;
        }
        /// <summary>
        /// DataTable转换成对象的List集合
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="_tab">DataTable</param>
        /// <returns></returns>
        public static List<object> DataTable2List(this DataTable _tab, Type type)
        {
            List<object> _ResultList = new List<object>();
            if (_tab == null) return _ResultList;

            var _RowMap = GetDataRow2EntityFunc(_tab, type);
            if (_RowMap == null) return _ResultList;
            foreach (DataRow info in _tab.Rows)
                _ResultList.Add(_RowMap(info, type));
            return _ResultList;
        }

        /// <summary>
        /// 通用类型转换 Convert.ChangeType
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="conversionType"></param>
        /// <returns></returns>
        public static object ConvertType(object obj, Type conversionType)
        {
            MethodInfo methodInfo = null;
            if (conversionType.IsValueType || conversionType == typeof(string))
                if (conversionType.IsEnum)
                    methodInfo = typeof(FuncTable2Entity).GetMethod("StrToEnum", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(conversionType);
                else if (conversionType == typeof(int?))
                    methodInfo = typeof(FuncTable2Entity).GetMethod("ToIntCuInt", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(conversionType);
                else if (conversionType == typeof(decimal?))
                    methodInfo = typeof(FuncTable2Entity).GetMethod("ToIntCuDecimal", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(conversionType);
                else if (conversionType == typeof(double?))
                    methodInfo = typeof(FuncTable2Entity).GetMethod("ToIntCuDouble", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(conversionType);
                else if (conversionType == typeof(DateTime?))
                    methodInfo = typeof(FuncTable2Entity).GetMethod("ToIntCuDateTime", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(conversionType);
                else if (conversionType == typeof(Guid))
                    methodInfo = typeof(FuncTable2Entity).GetMethod("ToGuid", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(conversionType);
                else
                    methodInfo = mConvertMets[conversionType];
            return methodInfo?.Invoke(obj, new object[] { obj });
        }
    }
}
